package uk.co.q3c.deplan.server.unit.domain.util;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.log4j.Logger;
import org.testng.Assert;
import org.testng.annotations.Test;

import uk.co.q3c.deplan.server.domain.DomainObjectComparator;
import uk.co.q3c.deplan.server.domain.DomainMatchingLog.Outcome;
import uk.co.q3c.deplan.server.util.ReflectionFieldBuilder.FieldTypes;

/**
 * @see DomainObjectComparator
 * @author DSowerby 20 Oct 2008
 * 
 */
@Test
public class DomainObjectComparator_UT {
	transient protected final Logger logger = Logger.getLogger(getClass().getName());

	enum Widget {
		BIG, SMALL, PIDDLY
	}

	class BlankObject {

	}

	class TestObject {

		int id;
		String s;
		long longint;
		double dble;
		char chr;
		boolean bool;
		float flote;
		short shrt;
		byte bite;

		TestObject2 tob2;
		Widget widget;
		List<Object> list = new ArrayList<Object>();

		Map<Object, Object> map = new HashMap<Object, Object>();

		int[] ints = { 3, 5 };

		@Override
		public String toString() {
			return getClass().getSimpleName() + " " + Integer.toString(id);
		}
	}

	class TestObject2 {
		TestObject parent;
		int id2;

		@Override
		public String toString() {
			return getClass().getSimpleName() + " " + Integer.toString(id2);
		}

	}

	class TestObjectSub extends TestObject {
	}

	public void strict() {
		DomainObjectComparator doc = new DomainObjectComparator();
		Assert.assertEquals(doc.compare(new TestObject(), new TestObject2()), Outcome.RULEFAIL);
		Assert.assertEquals(doc.compare(new TestObject(), new TestObject()), Outcome.PASS);

	}

	public void subClassOnly() {
		DomainObjectComparator doc = new DomainObjectComparator();
		// different classes - fail
		doc.setCriteria(DomainObjectComparator.Criteria.SUBCLASS);
		Assert.assertEquals(doc.compare(new TestObject(), new TestObject2()), Outcome.RULEFAIL);

		// A subclass of B - fail
		Assert.assertEquals(doc.compare(new TestObjectSub(), new TestObject()), Outcome.RULEFAIL);

		// Both same class - pass
		Assert.assertEquals(doc.compare(new TestObject(), new TestObject()), Outcome.PASS);

		// B subclass of A - pass
		Assert.assertEquals(doc.compare(new TestObject(), new TestObjectSub()), Outcome.PASS);

	}

	/**
	 * All default values, everything should match. NOTE remember the root
	 * object is counted as well
	 */
	public void fieldCheckAllDefaults() {
		DomainObjectComparator doc = new DomainObjectComparator();
		TestObject objectA = new TestObject();
		TestObject objectB = new TestObject();
		Outcome finalOutcome = doc.compare(objectA, objectB);

		Assert.assertEquals(doc.getLog().get(FieldTypes.ENUM, Outcome.PASS), 1);
		Assert.assertEquals(doc.getLog().get(FieldTypes.ENUM, Outcome.TOTAL), 1);
		Assert.assertEquals(doc.getLog().get(FieldTypes.PRIMITIVE, Outcome.PASS), 8);
		Assert.assertEquals(doc.getLog().get(FieldTypes.PRIMITIVE, Outcome.TOTAL), 8);
		Assert.assertEquals(doc.getLog().get(FieldTypes.STRING, Outcome.PASS), 1);
		Assert.assertEquals(doc.getLog().get(FieldTypes.STRING, Outcome.TOTAL), 1);
		Assert.assertEquals(doc.getLog().get(FieldTypes.MAP, Outcome.PASS), 1);
		Assert.assertEquals(doc.getLog().get(FieldTypes.MAP, Outcome.TOTAL), 1);
		Assert.assertEquals(doc.getLog().get(FieldTypes.COLLECTION, Outcome.PASS), 1);
		Assert.assertEquals(doc.getLog().get(FieldTypes.COLLECTION, Outcome.TOTAL), 1);
		Assert.assertEquals(doc.getLog().get(FieldTypes.ARRAY, Outcome.PASS), 1);
		Assert.assertEquals(doc.getLog().get(FieldTypes.ARRAY, Outcome.TOTAL), 1);
		Assert.assertEquals(doc.getLog().get(FieldTypes.GENERALOBJECT, Outcome.PASS), 2);
		Assert.assertEquals(doc.getLog().get(FieldTypes.GENERALOBJECT, Outcome.TOTAL), 2);
		Assert.assertEquals(finalOutcome, Outcome.PASS);

		// add a TestObject2 instance
		objectA.tob2 = new TestObject2();
		objectB.tob2 = new TestObject2();
		finalOutcome = doc.compare(objectA, objectB);

		// an extra primitive (id2)
		// and object (parent)
		Assert.assertEquals(doc.getLog().get(FieldTypes.ENUM, Outcome.PASS), 1);
		Assert.assertEquals(doc.getLog().get(FieldTypes.ENUM, Outcome.TOTAL), 1);
		Assert.assertEquals(doc.getLog().get(FieldTypes.PRIMITIVE, Outcome.PASS), 9);
		Assert.assertEquals(doc.getLog().get(FieldTypes.PRIMITIVE, Outcome.TOTAL), 9);
		Assert.assertEquals(doc.getLog().get(FieldTypes.STRING, Outcome.PASS), 1);
		Assert.assertEquals(doc.getLog().get(FieldTypes.STRING, Outcome.TOTAL), 1);
		Assert.assertEquals(doc.getLog().get(FieldTypes.MAP, Outcome.PASS), 1);
		Assert.assertEquals(doc.getLog().get(FieldTypes.MAP, Outcome.TOTAL), 1);
		Assert.assertEquals(doc.getLog().get(FieldTypes.COLLECTION, Outcome.PASS), 1);
		Assert.assertEquals(doc.getLog().get(FieldTypes.COLLECTION, Outcome.TOTAL), 1);
		Assert.assertEquals(doc.getLog().get(FieldTypes.ARRAY, Outcome.PASS), 1);
		Assert.assertEquals(doc.getLog().get(FieldTypes.ARRAY, Outcome.TOTAL), 1);
		Assert.assertEquals(doc.getLog().get(FieldTypes.GENERALOBJECT, Outcome.PASS), 3);
		Assert.assertEquals(doc.getLog().get(FieldTypes.GENERALOBJECT, Outcome.TOTAL), 3);
		Assert.assertEquals(finalOutcome, Outcome.PASS);

		// if parent really is parent could cause a loop
		objectA.tob2.parent = objectA;
		objectB.tob2.parent = objectB;
		finalOutcome = doc.compare(objectA, objectB);
		Assert.assertEquals(doc.getLog().get(FieldTypes.GENERALOBJECT, Outcome.PASS), 2);
		Assert.assertEquals(doc.getLog().get(FieldTypes.GENERALOBJECT, Outcome.LOOP), 1);
		Assert.assertEquals(doc.getLog().get(FieldTypes.GENERALOBJECT, Outcome.TOTAL), 3);
		Assert.assertEquals(finalOutcome, Outcome.PASS);

	}

	public void fieldFail() {
		DomainObjectComparator doc = new DomainObjectComparator();
		// doc.getLog().setLogAll(true);
		TestObject objectA = new TestObject();
		TestObject objectB = new TestObject();
		objectA.bool = true;
		objectB.bool = false;
		objectA.chr = 'g';
		objectB.chr = 'G';

		objectA.tob2 = new TestObject2();
		objectB.tob2 = new TestObject2();
		objectA.tob2.id2 = 3;

		Outcome finalOutcome = doc.compare(objectA, objectB);
		Assert.assertEquals(doc.getLog().get(FieldTypes.ENUM, Outcome.PASS), 1);
		Assert.assertEquals(doc.getLog().get(FieldTypes.ENUM, Outcome.TOTAL), 1);
		Assert.assertEquals(doc.getLog().get(FieldTypes.PRIMITIVE, Outcome.PASS), 6);
		Assert.assertEquals(doc.getLog().get(FieldTypes.PRIMITIVE, Outcome.FAIL), 3);
		Assert.assertEquals(doc.getLog().get(FieldTypes.PRIMITIVE, Outcome.TOTAL), 9);
		Assert.assertEquals(doc.getLog().get(FieldTypes.STRING, Outcome.PASS), 1);
		Assert.assertEquals(doc.getLog().get(FieldTypes.STRING, Outcome.TOTAL), 1);
		Assert.assertEquals(doc.getLog().get(FieldTypes.MAP, Outcome.PASS), 1);
		Assert.assertEquals(doc.getLog().get(FieldTypes.MAP, Outcome.TOTAL), 1);
		Assert.assertEquals(doc.getLog().get(FieldTypes.COLLECTION, Outcome.PASS), 1);
		Assert.assertEquals(doc.getLog().get(FieldTypes.COLLECTION, Outcome.TOTAL), 1);
		Assert.assertEquals(doc.getLog().get(FieldTypes.ARRAY, Outcome.PASS), 1);
		Assert.assertEquals(doc.getLog().get(FieldTypes.ARRAY, Outcome.TOTAL), 1);
		Assert.assertEquals(doc.getLog().get(FieldTypes.GENERALOBJECT, Outcome.FAIL), 2);
		Assert.assertEquals(doc.getLog().get(FieldTypes.GENERALOBJECT, Outcome.PASS), 1);
		Assert.assertEquals(doc.getLog().get(FieldTypes.GENERALOBJECT, Outcome.TOTAL), 3);
		Assert.assertEquals(finalOutcome, Outcome.FAIL);
	}

	public void listContainsObjects() {
		DomainObjectComparator doc = new DomainObjectComparator();
		TestObject objectA = new TestObject();
		TestObject objectB = new TestObject();
		objectA.list.add(new BlankObject());
		objectB.list.add(new BlankObject());
		Outcome finalOutcome = doc.compare(objectA, objectB);
		Assert.assertEquals(finalOutcome, Outcome.PASS);

		// list of different lengths
		objectB.list.add(new BlankObject());
		finalOutcome = doc.compare(objectA, objectB);
		Assert.assertEquals(finalOutcome, Outcome.FAIL);

		// different object types
		objectB.list.remove(0);
		objectB.list.remove(0);
		objectB.list.add(new TestObject2());
		finalOutcome = doc.compare(objectA, objectB);
		Assert.assertEquals(finalOutcome, Outcome.RULEFAIL);

		// contained objects fail to match at field level
		objectA.list.remove(0);
		objectA.list.add(new TestObject2());
		TestObject2 tb = (TestObject2) objectA.list.get(0);
		tb.id2 = 33;
		finalOutcome = doc.compare(objectA, objectB);
		Assert.assertEquals(finalOutcome, Outcome.FAIL);

	}

	public void mapContainsObjects() {
		DomainObjectComparator doc = new DomainObjectComparator();
		TestObject objectA = new TestObject();
		TestObject objectB = new TestObject();
		objectA.map.put("a", new TestObject2());
		objectB.map.put("a", new TestObject2());
		Outcome finalOutcome = doc.compare(objectA, objectB);
		Assert.assertEquals(finalOutcome, Outcome.PASS);

		// extra entry in B
		objectB.map.put("b", new TestObject2());
		finalOutcome = doc.compare(objectA, objectB);
		Assert.assertEquals(finalOutcome, Outcome.FAIL);

	}

}
